// --------------------------------------------------------------------------------------------------------------------
// <copyright company="" file="Deflater.cs">
//   
// </copyright>
// <summary>
//   (c) Copyright Ascensio System Limited 2008-2009
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace agsXMPP.IO.Compression
{
    #region usings

    using System;

    #endregion

    /// <summary>
    /// This is the Deflater class.  The deflater class compresses input
    /// with the deflate algorithm described in RFC 1951.  It has several
    /// compression levels and three different strategies described below.
    /// 
    /// This class is <i>not</i> thread safe.  This is inherent in the API, due
    /// to the split of deflate and setInput.
    /// 
    /// author of the original java version : Jochen Hoenicke
    /// </summary>
    public class Deflater
    {
        #region Members

        /// <summary>
        /// The best and slowest compression level.  This tries to find very
        /// long and distant string repetitions.
        /// </summary>
        public static int BEST_COMPRESSION = 9;

        /// <summary>
        /// The worst but fastest compression level.
        /// </summary>
        public static int BEST_SPEED = 1;

        /// <summary>
        /// </summary>
        private static int BUSY_STATE = 0x10;

        /// <summary>
        /// </summary>
        private static int CLOSED_STATE = 0x7f;

        /// <summary>
        /// The default compression level.
        /// </summary>
        public static int DEFAULT_COMPRESSION = -1;

        /// <summary>
        /// The compression method.  This is the only method supported so far.
        /// There is no need to use this constant at all.
        /// </summary>
        public static int DEFLATED = 8;

        /// <summary>
        /// </summary>
        private static int FINISHED_STATE = 0x1e;

        /// <summary>
        /// </summary>
        private static int FINISHING_STATE = 0x1c;

        /// <summary>
        /// </summary>
        private static int FLUSHING_STATE = 0x14;

        /*
		* The Deflater can do the following state transitions:
			*
			* (1) -> INIT_STATE   ----> INIT_FINISHING_STATE ---.
			*        /  | (2)      (5)                         |
			*       /   v          (5)                         |
			*   (3)| SETDICT_STATE ---> SETDICT_FINISHING_STATE |(3)
			*       \   | (3)                 |        ,-------'
			*        |  |                     | (3)   /
			*        v  v          (5)        v      v
			* (1) -> BUSY_STATE   ----> FINISHING_STATE
			*                                | (6)
			*                                v
			*                           FINISHED_STATE
			*    \_____________________________________/
			*          | (7)
			*          v
			*        CLOSED_STATE
			*
			* (1) If we should produce a header we start in INIT_STATE, otherwise
			*     we start in BUSY_STATE.
			* (2) A dictionary may be set only when we are in INIT_STATE, then
			*     we change the state as indicated.
			* (3) Whether a dictionary is set or not, on the first call of deflate
			*     we change to BUSY_STATE.
			* (4) -- intentionally left blank -- :)
			* (5) FINISHING_STATE is entered, when flush() is called to indicate that
			*     there is no more INPUT.  There are also states indicating, that
			*     the header wasn't written yet.
			* (6) FINISHED_STATE is entered, when everything has been flushed to the
			*     internal pending output buffer.
			* (7) At any time (7)
			*
			*/

        /// <summary>
        /// </summary>
        private static int INIT_STATE = 0x00;

        /// <summary>
        /// </summary>
        private static int IS_FINISHING = 0x08;

        /// <summary>
        /// </summary>
        private static int IS_FLUSHING = 0x04;

        /// <summary>
        /// </summary>
        private static int IS_SETDICT = 0x01;

        /// <summary>
        /// This level won't compress at all but output uncompressed blocks.
        /// </summary>
        public static int NO_COMPRESSION = 0;

        /// <summary>
        /// </summary>
        private static int SETDICT_STATE = 0x01;

        /// <summary>
        /// The deflater engine.
        /// </summary>
        private DeflaterEngine engine;

        // 		private static  int INIT_FINISHING_STATE    = 0x08;
        // 		private static  int SETDICT_FINISHING_STATE = 0x09;

        /// <summary>
        /// Compression level.
        /// </summary>
        private int level;

        /// <summary>
        /// If true no Zlib/RFC1950 headers or footers are generated
        /// </summary>
        private bool noZlibHeaderOrFooter;

        /// <summary>
        /// The pending output.
        /// </summary>
        private DeflaterPending pending;

        /// <summary>
        /// The current state.
        /// </summary>
        private int state;

        /// <summary>
        /// The total bytes of output written.
        /// </summary>
        private long totalOut;

        #endregion

        #region Constructor

        /// <summary>
        /// Creates a new deflater with default compression level.
        /// </summary>
        public Deflater() : this(DEFAULT_COMPRESSION, false) {}

        /// <summary>
        /// Creates a new deflater with given compression level.
        /// </summary>
        /// <param name="lvl">
        /// the compression level, a value between NO_COMPRESSION
        /// and BEST_COMPRESSION, or DEFAULT_COMPRESSION.
        /// </param>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// if lvl is out of range.
        /// </exception>
        public Deflater(int lvl) : this(lvl, false) {}

        /// <summary>
        /// Creates a new deflater with given compression level.
        /// </summary>
        /// <param name="level">
        /// the compression level, a value between NO_COMPRESSION
        /// and BEST_COMPRESSION.
        /// </param>
        /// <param name="noZlibHeaderOrFooter">
        /// true, if we should suppress the Zlib/RFC1950 header at the
        /// beginning and the adler checksum at the end of the output.  This is
        /// useful for the GZIP/PKZIP formats.
        /// </param>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// if lvl is out of range.
        /// </exception>
        public Deflater(int level, bool noZlibHeaderOrFooter)
        {
            if (level == DEFAULT_COMPRESSION)
            {
                level = 6;
            }
            else if (level < NO_COMPRESSION || level > BEST_COMPRESSION)
            {
                throw new ArgumentOutOfRangeException("level");
            }

            pending = new DeflaterPending();
            engine = new DeflaterEngine(pending);
            this.noZlibHeaderOrFooter = noZlibHeaderOrFooter;
            SetStrategy(DeflateStrategy.Default);
            SetLevel(level);
            Reset();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the current adler checksum of the data that was processed so far.
        /// </summary>
        public int Adler
        {
            get { return engine.Adler; }
        }

        /// <summary>
        /// Returns true if the stream was finished and no more output bytes
        /// are available.
        /// </summary>
        public bool IsFinished
        {
            get { return state == FINISHED_STATE && pending.IsFlushed; }
        }

        /// <summary>
        /// Returns true, if the input buffer is empty.
        /// You should then call setInput(). 
        /// NOTE: This method can also return true when the stream
        /// was finished.
        /// </summary>
        public bool IsNeedingInput
        {
            get { return engine.NeedsInput(); }
        }

        /// <summary>
        /// Gets the number of input bytes processed so far.
        /// </summary>
        public int TotalIn
        {
            get { return engine.TotalIn; }
        }

        /// <summary>
        /// Gets the number of output bytes so far.
        /// </summary>
        public long TotalOut
        {
            get { return totalOut; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Resets the deflater.  The deflater acts afterwards as if it was
        /// just created with the same compression level and strategy as it
        /// had before.
        /// </summary>
        public void Reset()
        {
            state = noZlibHeaderOrFooter ? BUSY_STATE : INIT_STATE;
            totalOut = 0;
            pending.Reset();
            engine.Reset();
        }

        /// <summary>
        /// Flushes the current input block.  Further calls to deflate() will
        /// produce enough output to inflate everything in the current input
        /// block.  This is not part of Sun's JDK so I have made it package
        /// private.  It is used by DeflaterOutputStream to implement
        /// flush().
        /// </summary>
        public void Flush()
        {
            state |= IS_FLUSHING;
        }

        /// <summary>
        /// Finishes the deflater with the current input block.  It is an error
        /// to give more input after this method was called.  This method must
        /// be called to force all bytes to be flushed.
        /// </summary>
        public void Finish()
        {
            state |= IS_FLUSHING | IS_FINISHING;
        }

        /// <summary>
        /// Sets the data which should be compressed next.  This should be only
        /// called when needsInput indicates that more input is needed.
        /// If you call setInput when needsInput() returns false, the
        /// previous input that is still pending will be thrown away.
        /// The given byte array should not be changed, before needsInput() returns
        /// true again.
        /// This call is equivalent to 
        /// <code>
        /// setInput(input, 0, input.length)
        /// </code>
        /// .
        /// </summary>
        /// <param name="input">
        /// the buffer containing the input data.
        /// </param>
        /// <exception cref="System.InvalidOperationException">
        /// if the buffer was finished() or ended().
        /// </exception>
        public void SetInput(byte[] input)
        {
            SetInput(input, 0, input.Length);
        }

        /// <summary>
        /// Sets the data which should be compressed next.  This should be
        /// only called when needsInput indicates that more input is needed.
        /// The given byte array should not be changed, before needsInput() returns
        /// true again.
        /// </summary>
        /// <param name="input">
        /// the buffer containing the input data.
        /// </param>
        /// <param name="off">
        /// the start of the data.
        /// </param>
        /// <param name="len">
        /// the length of the data.
        /// </param>
        /// <exception cref="System.InvalidOperationException">
        /// if the buffer was finished() or ended() or if previous input is still pending.
        /// </exception>
        public void SetInput(byte[] input, int off, int len)
        {
            if ((state & IS_FINISHING) != 0)
            {
                throw new InvalidOperationException("finish()/end() already called");
            }

            engine.SetInput(input, off, len);
        }

        /// <summary>
        /// Sets the compression level.  There is no guarantee of the exact
        /// position of the change, but if you call this when needsInput is
        /// true the change of compression level will occur somewhere near
        /// before the end of the so far given input.
        /// </summary>
        /// <param name="lvl">
        /// the new compression level.
        /// </param>
        public void SetLevel(int lvl)
        {
            if (lvl == DEFAULT_COMPRESSION)
            {
                lvl = 6;
            }
            else if (lvl < NO_COMPRESSION || lvl > BEST_COMPRESSION)
            {
                throw new ArgumentOutOfRangeException("lvl");
            }

            if (level != lvl)
            {
                level = lvl;
                engine.SetLevel(lvl);
            }
        }

        /// <summary>
        /// Get current compression level
        /// </summary>
        /// <returns>
        /// Returns the current compression level
        /// </returns>
        public int GetLevel()
        {
            return level;
        }

        /// <summary>
        /// Sets the compression strategy. Strategy is one of
        /// DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED.  For the exact
        /// position where the strategy is changed, the same as for
        /// setLevel() applies.
        /// </summary>
        /// <param name="strategy">
        /// The new compression strategy.
        /// </param>
        public void SetStrategy(DeflateStrategy strategy)
        {
            engine.Strategy = strategy;
        }

        /// <summary>
        /// Deflates the current input block with to the given array.
        /// </summary>
        /// <param name="output">
        /// The buffer where compressed data is stored
        /// </param>
        /// <returns>
        /// The number of compressed bytes added to the output, or 0 if either
        /// needsInput() or finished() returns true or length is zero.
        /// </returns>
        public int Deflate(byte[] output)
        {
            return Deflate(output, 0, output.Length);
        }

        /// <summary>
        /// Deflates the current input block to the given array.
        /// </summary>
        /// <param name="output">
        /// Buffer to store the compressed data.
        /// </param>
        /// <param name="offset">
        /// Offset into the output array.
        /// </param>
        /// <param name="length">
        /// The maximum number of bytes that may be stored.
        /// </param>
        /// <returns>
        /// The number of compressed bytes added to the output, or 0 if either
        /// needsInput() or finished() returns true or length is zero.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">
        /// If end() was previously called.
        /// </exception>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// If offset and/or length don't match the array length.
        /// </exception>
        public int Deflate(byte[] output, int offset, int length)
        {
            int origLength = length;

            if (state == CLOSED_STATE)
            {
                throw new InvalidOperationException("Deflater closed");
            }

            if (state < BUSY_STATE)
            {
                /* output header */
                int header = (DEFLATED + ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8;
                int level_flags = (level - 1) >> 1;
                if (level_flags < 0 || level_flags > 3)
                {
                    level_flags = 3;
                }

                header |= level_flags << 6;
                if ((state & IS_SETDICT) != 0)
                {
                    /* Dictionary was set */
                    header |= DeflaterConstants.PRESET_DICT;
                }

                header += 31 - (header%31);

                pending.WriteShortMSB(header);
                if ((state & IS_SETDICT) != 0)
                {
                    int chksum = engine.Adler;
                    engine.ResetAdler();
                    pending.WriteShortMSB(chksum >> 16);
                    pending.WriteShortMSB(chksum & 0xffff);
                }

                state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING));
            }

            for (;;)
            {
                int count = pending.Flush(output, offset, length);
                offset += count;
                totalOut += count;
                length -= count;

                if (length == 0 || state == FINISHED_STATE)
                {
                    break;
                }

                if (!engine.Deflate((state & IS_FLUSHING) != 0, (state & IS_FINISHING) != 0))
                {
                    if (state == BUSY_STATE)
                    {
                        /* We need more input now */
                        return origLength - length;
                    }
                    else if (state == FLUSHING_STATE)
                    {
                        if (level != NO_COMPRESSION)
                        {
                            /* We have to supply some lookahead.  8 bit lookahead
							 * is needed by the zlib inflater, and we must fill
							 * the next byte, so that all bits are flushed.
							 */
                            int neededbits = 8 + ((-pending.BitCount) & 7);
                            while (neededbits > 0)
                            {
                                /* write a static tree block consisting solely of
								 * an EOF:
								 */
                                pending.WriteBits(2, 10);
                                neededbits -= 10;
                            }
                        }

                        state = BUSY_STATE;
                    }
                    else if (state == FINISHING_STATE)
                    {
                        pending.AlignToByte();

                        // Compressed data is complete.  Write footer information if required.
                        if (!noZlibHeaderOrFooter)
                        {
                            int adler = engine.Adler;
                            pending.WriteShortMSB(adler >> 16);
                            pending.WriteShortMSB(adler & 0xffff);
                        }

                        state = FINISHED_STATE;
                    }
                }
            }

            return origLength - length;
        }

        /// <summary>
        /// Sets the dictionary which should be used in the deflate process.
        /// This call is equivalent to 
        /// <code>
        /// setDictionary(dict, 0, dict.Length)
        /// </code>
        /// .
        /// </summary>
        /// <param name="dict">
        /// the dictionary.
        /// </param>
        /// <exception cref="System.InvalidOperationException">
        /// if setInput () or deflate () were already called or another dictionary was already set.
        /// </exception>
        public void SetDictionary(byte[] dict)
        {
            SetDictionary(dict, 0, dict.Length);
        }

        /// <summary>
        /// Sets the dictionary which should be used in the deflate process.
        /// The dictionary is a byte array containing strings that are
        /// likely to occur in the data which should be compressed.  The
        /// dictionary is not stored in the compressed output, only a
        /// checksum.  To decompress the output you need to supply the same
        /// dictionary again.
        /// </summary>
        /// <param name="dict">
        /// The dictionary data
        /// </param>
        /// <param name="offset">
        /// An offset into the dictionary.
        /// </param>
        /// <param name="length">
        /// The length of the dictionary data to use
        /// </param>
        /// <exception cref="System.InvalidOperationException">
        /// If setInput () or deflate () were already called or another dictionary was already set.
        /// </exception>
        public void SetDictionary(byte[] dict, int offset, int length)
        {
            if (state != INIT_STATE)
            {
                throw new InvalidOperationException();
            }

            state = SETDICT_STATE;
            engine.SetDictionary(dict, offset, length);
        }

        #endregion
    }
}